# -*- coding: utf8 -*-

__author__ = "Sergey Dryabzhinsky"

__doc__ = """
Обработка изображения

@author: Sergey Dryabzhinsky
"""

import math
from logger import get_file_logger
from multiprocessing import current_process
from threading import current_thread
import traceback
import numpy as np
import cv2

try:
    import Image
    from Image import BICUBIC, FLIP_LEFT_RIGHT
except:
    from PIL import Image
    from PIL.Image import BICUBIC, FLIP_LEFT_RIGHT


class iPIL( object ):
    filename = None

    calc_w = 64
    calc_h = 64

    cv_w = 1024
    cv_h = 1024
    cv_maxpoints = 400
    cv_patchsz = 24

    img = None

    log = None

    def get_logger(self):
        if self.log is None:
            self.log = get_file_logger(
                self.__class__.__name__ + "[%s-%s]" % (current_process( ).pid, current_thread( ).getName( )) )
        return self.log

    def is_image(self, filename):
        is_img = False
        try:
            img = self.load( filename )
            img.verify( )
            is_img = True
        except Exception as e:
            self.get_logger().error("is_image: %s" % e)
            self.get_logger().debug("is_image:\n" + traceback.format_exc())
            pass
        self.close( )
        return is_img

    def close(self):
        if self.img:
            self.img = None
            self.filename = None
        return self

    def load(self, filename):
        self.img = Image.open( filename )
        return self.img

    def width(self):
        if self.img:
            return self.img.size[ 0 ]
        return 0

    def height(self):
        if self.img:
            return self.img.size[ 1 ]
        return 0

    def process_image(self, mirror = False, parts = 100):
        """
        Process image data and calculate some results

        @return:    calculated data:
                    {
                        "color" : bool,
                        "mean_gray" : int,
                        "vector" : list
                    }
        @rtype:     dict
        """

        result = {
            "color": False,
            "mean_gray": 0,
            "vector": None
        }

        img = self.img

        if mirror:
            img = img.transpose( FLIP_LEFT_RIGHT )

        vector = { }.fromkeys( range( parts ), 0 )

        w, h = img.size

        if w == 0 or h == 0:
            vector = range( parts )
            vector = list( map( lambda item: 0, vector ) )
            result[ "vector" ] = tuple( vector )
            return result

        vw = math.sqrt( float( w ) / float( h ) * parts )
        vh = math.sqrt( float( h ) / float( w ) * parts )

        # resample for bicubic
        rsw = int( math.ceil( vw ) * 2 )
        rsh = int( math.ceil( vh ) * 2 )

        img = img.resize( (rsw, rsh), BICUBIC )

        wr = range( rsw )
        hr = range( rsh )

        meanColor = 0

        pal = [ ]
        if img.mode == "P":
            pal = img.getpalette( )

        color = 0
        for x in wr:
            for y in hr:
                r = g = b = 0
                if img.mode == "RGBA":
                    r, g, b, a = img.getpixel( ( x, y ) )
                    #mc = math.sqrt(r * r * 0.241 + g * g * 0.691 + b * b * 0.068)
                elif img.mode == "RGB":
                    r, g, b = img.getpixel( ( x, y ) )
                    #mc = math.sqrt(r * r * 0.241 + g * g * 0.691 + b * b * 0.068)
                elif img.mode == "P":
                    ind = img.getpixel( ( x, y ) )
                    r, g, b = pal[ ind * 3:ind * 3 + 3 ]
                    #mc = math.sqrt(r * r * 0.241 + g * g * 0.691 + b * b * 0.068)
                elif img.mode == "L":
                    r = g = b = img.getpixel( ( x, y ) )

                if r != g or g != b:
                    color += 1

                # Y = 0.5 * (max( (r, g, b,) ) + min( (r, g, b,) ))
                Y = math.sqrt(r * r * 0.241 + g * g * 0.691 + b * b * 0.068)

                vn = int( math.floor( float( x * rsh + y ) / float( rsw * rsh ) * (vw * vh) ) )

                if vector.get( vn, 0 ) == 0:
                    vector[ vn ] = {
                        "c": 0,
                        "n": 0
                    }
                vector[ vn ][ "c" ] += Y
                vector[ vn ][ "n" ] += 1

                meanColor += Y

        if color:
            result[ "color" ] = True

        meanColor /= rsw * rsh
        result[ "mean_gray" ] = int( meanColor )

        rv = ()
        for key in range( parts ):
            item = vector.get( key )
            if not item or not item[ "n" ]:
                rv += (0,)
            else:
                rv += (int( item[ "c" ] * 1.0 / item[ "n" ] ),)

        if len(rv) != parts:
            raise ValueError("Length of vector not equal parts count")

        result[ "vector" ] = rv
        return result

    def isColour(self):

        img = self.img

        result = False

        w, h = img.size

        if w == 0 or h == 0:
            return result

        img = img.resize( (self.calc_w, self.calc_h), BICUBIC )

        pal = [ ]
        if img.mode == "P":
            pal = img.getpalette( )

        for x in range(self.calc_w):
            if result:
                break
            for y in range(self.calc_h):
                r = g = b = 0
                if img.mode == "RGBA":
                    r, g, b, a = img.getpixel( ( x, y ) )
                    #mc = math.sqrt(r * r * 0.241 + g * g * 0.691 + b * b * 0.068)
                elif img.mode == "RGB":
                    r, g, b = img.getpixel( ( x, y ) )
                    #mc = math.sqrt(r * r * 0.241 + g * g * 0.691 + b * b * 0.068)
                elif img.mode == "P":
                    ind = img.getpixel( ( x, y ) )
                    r, g, b = pal[ ind * 3:ind * 3 + 3 ]
                    #mc = math.sqrt(r * r * 0.241 + g * g * 0.691 + b * b * 0.068)
                elif img.mode == "L":
                    r = g = b = img.getpixel( ( x, y ) )

                if r != g or g != b:
                    result = True
                    break

        return result

    def imageMean(self):
        """
        Process image data and calculate some results

        @return:    64bit integer
        @rtype:     int
        """
        result = 0

        img = self.img

        w, h = img.size

        if w == 0 or h == 0:
            return result

        img = img.resize( (1, 1), BICUBIC )
        img = img.convert("L")

        result = img.getpixel( ( 0, 0 ) )

        return result

    def imageToInt64(self):
        """
        Process image data and calculate some results

        @return:    64bit integer
        @rtype:     int
        """
        result = 0

        img = self.img

        w, h = img.size

        if w == 0 or h == 0:
            return result

        img = img.resize( (8, 8), BICUBIC )
        img = img.convert("1")

        index = 0
        for x in range(8):
            for y in range(8):
                c = img.getpixel( ( x, y ) )
                if c:
                    result += 2 ** index
                index += 1

        return result

    def imageTo16Int64(self):
        """
        Process image data and calculate some results

        @return:    16 64bit integer
        @rtype:     list
        """

        img = self.img

        w, h = img.size

        if w == 0 or h == 0:
            result = []
            for i in range(16):
                result.append(0)

            return result

        img = img.resize( (self.calc_w, self.calc_h), BICUBIC )
        img = img.convert("1")

        result = []
        for i in range(4):
            for j in range(4):
                index = 0
                ij_result = 0
                for x in range(8):
                    for y in range(8):
                        c = img.getpixel( ( i*8 + x, j*8+y ) )
                        if c:
                            ij_result += 2 ** index
                        index += 1
                result.append(ij_result)

        return result

    def imageToBitStr(self):
        """
        Process image data and calculate some results

        @return:    byte string with 1 color image mask
        @rtype:     bytes
        """

        img = self.img

        w, h = img.size

        if w == 0 or h == 0:
            result = b''
            for i in range(self.calc_w * self.calc_h):
                result += chr(0)

            return result

        img = img.resize( (self.calc_w, self.calc_h), BICUBIC )
        img = img.convert("1")

        result = b''
        for y in range(self.calc_h):
            for x in range(self.calc_w):
                c = img.getpixel( ( x, y, ) )
                result += chr(c)

        return result

    def imageToGrayStr(self):
        """
        Process image data and calculate some results

        @return:    byte string with gray color image mask
        @rtype:     bytes
        """

        img = self.img

        w, h = img.size

        if w == 0 or h == 0:
            result = b''
            for i in range(self.calc_w * self.calc_h):
                result += chr(0)

            return result

        img = img.resize( (self.calc_w, self.calc_h), BICUBIC )
        img = img.convert("L")

        result = b''
        for y in range(self.calc_h):
            for x in range(self.calc_w):
                c = img.getpixel( ( x, y, ) )
                result += chr(c)

        return result

    def imageToOpenCvDescriptors(self):
        descriptors = np.array([])
        try:

            img = self.img
            img = img.resize( (self.cv_w, self.cv_h), BICUBIC )
            img = img.convert("RGB")

            open_cv_image = np.array(img)
            # Convert RGB to BGR
            open_cv_image = open_cv_image[:, :, ::-1].copy()

            orb = cv2.ORB(self.cv_maxpoints, 1.1101, 64, self.cv_patchsz, 0, 3, 0, self.cv_patchsz)
            kp, descriptors = orb.detectAndCompute(open_cv_image,None)
        except:
            pass
        return descriptors

    def compare_vectors(self, v1, v2, min_diff=0, correction=0, parts=100):
        n_eq = 0
        for key in range(parts):
            p1 = v1[key]
            p2 = v2[key]
            p = max((p1, p2,))
            if not p:
                n_eq += 1
            # If difference less then minimal - equal
            elif abs(p1 - p2 + correction) / float(p) <= min_diff:
                n_eq += 1

        return n_eq

    def compare_bit_vectors(self, v1, v2):
        """
        @return: percent equality
        @rtype: float
        """
        n_eq = 0
        vlen = min(len(v1), len(v2))
        for key in range(vlen):
            p1 = v1[key]
            p2 = v2[key]
            if p1 == p2:
                n_eq += 1

        if vlen:
            return int(n_eq * 100.0 / vlen)
        return 0

    def compare_gray_vectors(self, v1, v2):
        """
        @return: percent equality
        @rtype: float
        """
        n_eq = 0
        vlen = min(len(v1), len(v2))
        for key in range(vlen):
            p1 = v1[key]
            p2 = v2[key]
            n_eq += 1.0 * min(p1, p2) / max(p1, p2)

        if vlen:
            return int(n_eq * 100.0 / vlen)
        return 0

    def compare_opencv_descriptors(self, desc1, desc2):
        """
        @return: percent equality
        @rtype: float
        """

        # create BFMatcher object
        bf = cv2.BFMatcher(cv2.NORM_HAMMING2, crossCheck=True)

        # Match descriptors.
        matches = bf.match(desc1,desc2)

        # Sort them in the order of their distance.
        matches = sorted(matches, key = lambda x:x.distance)
        good = []
        for m in matches:
            if m.distance <= self.cv_patchsz:
                good.append(m)

        LG=len(good)
        LM=len(matches)
        p1 = 400.0 * LG / self.cv_maxpoints
        p2 = 200.0 * LG / LM

        return (p1 + p2) / 2

